#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <signal.h>

#include "httpd.h"
#include "utils.h"
#include "link.h"

// max number for connections
#define CONN_MAX 1000

static int listenfd, clients[CONN_MAX];
static void startServer(const char *);
static void respond(int);
static char * get_post_data_key(char *);

typedef struct { char *name, *value; } header_t;
static header_t req_header[17] = { {"\0", "\0"} };
static int clientfd;

static char * buf;
// POST 数据标志
static char post_data_flag[] = "Content-Disposition: form-data;";

void serve_forever(const char *PORT)
{
    struct sockaddr_in clientaddr;
    socklen_t addrlen;
    int slot=0;
    
    printf("Server started %shttp://127.0.0.1:%s%s\n", "\033[92m",PORT,"\033[0m");

    // Setting all elements to -1: signifies there is no client connected
    int i;
    for (i = 0; i < CONN_MAX; i++) {
        clients[i]=-1;
    }
    startServer(PORT);
    
    // Ignore SIGCHLD to avoid zombie threads
    signal(SIGCHLD,SIG_IGN);

    // ACCEPT connections
    while (1)
    {
        addrlen = sizeof(clientaddr);
        clients[slot] = accept (listenfd, (struct sockaddr *) &clientaddr, &addrlen);

        if (clients[slot] < 0) {
            perror("accept() error");
        } else {
            if ( fork()==0 ) {
                respond(slot);
                exit(0);
            }
        }

        while (clients[slot]!=-1) {
            slot = (slot+1) % CONN_MAX;
        }
    }
}

//start server
void startServer(const char *port)
{
    struct addrinfo hints, *res, *p;

    // getaddrinfo for host
    memset (&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    if (getaddrinfo( NULL, port, &hints, &res) != 0) {
        perror ("getaddrinfo() error");
        exit(1);
    }
    // socket and bind
    for (p = res; p != NULL; p = p->ai_next) {
        int option = 1;
        listenfd = socket (p->ai_family, p->ai_socktype, 0);
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
        if (listenfd == -1) continue;
        if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0) break;
    }
    if (p==NULL) {
        perror ("socket() or bind()");
        exit(1);
    }

    freeaddrinfo(res);

    // listen for incoming connections
    if ( listen (listenfd, 1000000) != 0 ) {
        perror("listen() error");
        exit(1);
    }
}


// get request header
char *request_header(const char* name)
{
    header_t *h = req_header;
    while(h->name) {
        if (strcmp(h->name, name) == 0) {
            return h->value;
        }
        h++;
    }
    return NULL;
}

//client connection
void respond(int n)
{
    int rcvd;

    buf = malloc(65535);
    rcvd = recv(clients[n], buf, 65535, 0);
    if (rcvd < 0) {
        fprintf(stderr,("recv() error\n"));
    } else if (rcvd==0)  {
        fprintf(stderr,"Client disconnected upexpectedly.\n");
    } else { // message received
        fprintf(stderr, "rcvd: %d: \n", rcvd);
        buf[rcvd] = '\0';
        method = strtok(buf,  " \t\r\n");
        uri    = strtok(NULL, " \t");
        protocol   = strtok(NULL, " \t\r\n"); 

        fprintf(stderr, "\x1b[32m + [%s] %s\x1b[0m\n", method, uri);
        
        if ( (query_string = strchr(uri, '?')) != NULL ) {
            *query_string++ = '\0'; //split URI
        } else {
            query_string = uri - 1; //use an empty string
        }
        header_t * h = req_header;
        char * t = NULL, *t2;
        while(h < req_header+16) {
            char *k,*v,*t;
            // NULL 的作用只是为了使得每次调用时不从头开始查找，而是从上次停止的位置开始查找
            k = strtok(NULL, "\r\n: \t"); if (!k) break;
            v = strtok(NULL, "\r\n");     while(*v && *v==' ') v++;
            h->name  = k;
            h->value = v;
            h++;
            fprintf(stderr, "[H] %s: %s\n", k, v);
            t = v + 1 + strlen(v);
            if (t[1] == '\r' && t[2] == '\n') break;
        }
        t++; // now the *t shall be the beginning of user payload

        t2 = request_header("Content-Length"); // and the related header if there is
        post_data_length = t2 ? atol(t2) : (rcvd-(t-buf));
        fprintf(stderr, "[Content-Length] : %s\n", t2);

        // collect post data
        char *row;
        param_t *param;
        post_data = new_link_list();
        while ((row = strtok(NULL, "\r\n")) != NULL) {
			param = (param_t *) malloc(sizeof(param_t));
			memset(param, 0x00, sizeof(param_t));
            if (strstr(row, post_data_flag) != NULL) {
                param->key = get_post_data_key(row);
                param->value = strtok(NULL, "\r\n");
                link_list_add(post_data, param);
                fprintf(stderr, "key: %s, value: %s\n", param->key, param->value);
            }
        }

        // bind clientfd to stdout, making it easier to write
        clientfd = clients[n];
        dup2(clientfd, STDOUT_FILENO);
        close(clientfd);

        // call router
        route();

        // tidy up
        fflush(stdout);
        shutdown(STDOUT_FILENO, SHUT_WR);
        close(STDOUT_FILENO);
    }

    //Closing SOCKET, all further send and recieve operations are DISABLED
    shutdown(clientfd, SHUT_RDWR);
    close(clientfd);
    free(buf);
    clients[n]=-1;
}

/**
 * collect parameter name from http request data row
 * @param row
 * @return
 */
char * get_post_data_key(char *row)
{
    int data_size;
    char *data, *index = NULL, *temp, *key;
    data_size = strlen(row) - strlen(post_data_flag);
    data = malloc(data_size);
    strncpy(data, row+strlen(post_data_flag), data_size);
    if ((index = strstr(data, "=")) == NULL) {
        return NULL;
    }
	temp = trim(++index, ' ');
	temp = trim(temp, '"');
	key = malloc(30);
	// create a new memory and return the pointer
	memcpy(key, temp, 30);
    free(data);
	return key;
}

/**
 * parse url query string, wrap the parameter to param_t
 * @param query_string
 * @return a link list of param_t
 */
link_list_t * url_params_parse(char *query_string)
{
    if (query_string == NULL) {
        return NULL;
    }
    char *segment, *index;
    link_list_t *link;
    param_t *param;
    link = (link_list_t *) new_link_list();
    segment = strtok(query_string, "&");
    while (segment != NULL) {
        if ((index = strstr(segment, "=")) != NULL) {
            param = malloc(sizeof(param_t));
            memset(param, 0x00, sizeof(param_t));
            *index++ = '\0';
            param->key = index;
            param->value = segment;
            link_list_add(link, param);
        }
        segment = strtok(NULL, "&");
    }
    return link;

}